/*
 * BplusTreeFile.cpp
 *
 *  Created on: Oct 31, 2012
 *      Author: m
 */

#include "BPlusTreeFile.h"

const char BPlusTreeFile::ControlBlock::magicNumberFirstDigit  = 'T';
const char BPlusTreeFile::ControlBlock::magicNumberSecondDigit = 'F';

BPlusTreeFile::BPlusTreeFile( const string& filename, size_t blockSize, bool trunc ) :
        m_file( blockSize )
{
    node_ptr root;

    if ( trunc || !this->fileExists( filename ) )
    {
        m_blockSize = blockSize;
        m_file.open( filename.c_str(),
                std::ios_base::in | std::ios_base::out | std::ios_base::trunc );

        ControlBlock initialControlBlock( m_blockSize, TF_INITIAL_ROOT_NBR );

        this->saveControlData( initialControlBlock );

        root = this->getNewNode( LEAF_LEVEL );

        assert( root->getNodeID() == TF_INITIAL_ROOT_NBR );
    }
    else
    {

    	m_blockSize = this->autodetectBlockSize( filename.c_str() );
        m_file.resetBlockSize( m_blockSize );

        m_file.open( filename.c_str(), std::ios_base::in | std::ios_base::out );

        // TODO: tirar una exception personalizada si la información de control no es válida!!
        assert( this->loadControlData().isValid() );
    }
}

BPlusTreeFile::~BPlusTreeFile()
{
    m_file.close();
}

node_ptr BPlusTreeFile::getNewRoot( const NodeLevel_t& level )
{
    node_ptr newRoot;
    newRoot = this->getNewNode( level );
    this->registerNodeAsRoot( newRoot );
    return newRoot;
}

node_ptr BPlusTreeFile::getRoot()
{
    NodeID_t rootNodeId;
    rootNodeId = this->loadControlData().getRootNodeNbr();

    return this->getNode( rootNodeId );
}

node_ptr BPlusTreeFile::getNewNode( const NodeLevel_t& level )
{
    node_ptr newNode;
    physical::blockNumber newNodeId;

    newNodeId = this->getNewBlockNbr();

    if ( level == LEAF_LEVEL )
    {
        newNode.reset(
                new Leaf( m_blockSize, newNodeId, &getLeafNodeSize ) );
    }
    else
    {
        newNode.reset(
                new Internal( m_blockSize, level, newNodeId,
                        &getInternalNodeSize,
                        &cropKey_func, this ) );
    }

    this->saveNode( newNode );
    return newNode;
}

node_ptr BPlusTreeFile::getNode( const NodeID_t& nodeNbr )
{
    physical::UP_byteStream buffer;
    node_ptr nodePtr;
    assert( nodeNbr > 0 );
    // Leo el bloque nro = id
    buffer.reset( new physical::byte[m_blockSize] );
    // Leo el bloque que contiene el nodo
    m_file.readBlock( nodeNbr, buffer.get() );
    // Verifico que dicho bloque no esté libre
    // TODO: por el momento no se hace porque no hay liberación de bloques.
    // Parseo el nodo del bloque y lo devuelvo
    this->buildNode( buffer.get(), nodePtr, nodeNbr );
    // lo devuelvo
    return nodePtr;
}

void BPlusTreeFile::saveNode( const node_ptr& node )
{
    Leaf* leafPtr;
    Internal* internalPtr;
    physical::UP_byteStream buffer( new physical::byte[m_blockSize] );

    assert( node->getNodeID() > 0 );

    if ( node->getLevel() == LEAF_LEVEL )
    {
        // construyo el bloque de serialización de nodo hoja

        leafPtr = ( Leaf* ) node.get();

        m_leafNodeSerializer.serialize( leafPtr->getSerializationBlock(), buffer.get() );
    }
    else
    {
        // construyo el bloque de serialización de nodo interno
        internalPtr = ( Internal* ) node.get();

        m_internalNodeSerializer.serialize( internalPtr->getSerializationBlock(), buffer.get() );
    }

    m_file.writeBlock( node->getNodeID(), buffer.get() );
}

void BPlusTreeFile::deleteNode( const NodeID_t& id )
{
    //    Not Implemented
}

void BPlusTreeFile::buildNode( const physical::byteStream block,
        node_ptr& node, const NodeID_t& nodeNbr )
{
    // Dado un bloque con un nodo serializado adentro
    // tengo q extraer el nivel del nodo ( 1eros 4 bytes)
    NodeLevel_t level;
    Node* nodePtr;
    LeafSerializationBlock leafSerializationBlock;
    InternalSerializationBlock internalSerializationBlock;
    memcpy( &level, block, sizeof(NodeLevel_t) );
    if ( level == LEAF_LEVEL )
    {
        m_leafNodeSerializer.deserialize( block, leafSerializationBlock );
        nodePtr = new Leaf( m_blockSize, nodeNbr,
                leafSerializationBlock.rightSibling,
                leafSerializationBlock.content, &getLeafNodeSize );
    }
    else
    {
        m_internalNodeSerializer.deserialize( block,
                internalSerializationBlock );
        nodePtr = new Internal( m_blockSize, internalSerializationBlock.level,
                nodeNbr, internalSerializationBlock.firstChild,
                internalSerializationBlock.content, &getInternalNodeSize, &cropKey_func, this );
    }

    node.reset( nodePtr );
}

void BPlusTreeFile::registerNodeAsRoot( node_ptr& node )
{
    ControlBlock controlData = this->loadControlData();

    controlData.setRootNodeNbr( node->getNodeID() );

    this->saveControlData( controlData );
}

BPlusTreeFile::ControlBlock BPlusTreeFile::loadControlData()
{
    physical::UP_byteStream buffer( new physical::byte[m_blockSize] );

    m_file.readBlock( TF_CONTROL_BLOCK_NBR, buffer.get() );

    return ControlBlock( buffer.get() );
}

void BPlusTreeFile::saveControlData( const ControlBlock& controlBlock )
{
    physical::UP_byteStream buffer( new physical::byte[m_blockSize] );

    controlBlock.writeInBlock( buffer.get() );

    m_file.writeBlock( TF_CONTROL_BLOCK_NBR, buffer.get() );
}

physical::blockNumber BPlusTreeFile::getNewBlockNbr()
{
    // Si hay bloques libres devuelvo el bloque libre
    // sino, devuelvo el nro del proximo bloque a crear
    // TODO: agregar lógica de control de bloques libres
    //  por ahora no implementada porque no se liberan bloques
    return m_file.getBlocksCount();
}

bool BPlusTreeFile::fileExists( const string& filename )
{
    bool openFailed;
    ifstream file( filename.c_str() );
    openFailed = file.fail();
    file.close();
    return !openFailed;
}

size_t BPlusTreeFile::autodetectBlockSize( const string& filename )
{
    ifstream file;
    size_t blockSize;
    // TODO: Tirar una exception PERSONALIZADA si no se pudo autodetectar el tamaño!!
    file.open( filename.c_str() );
    file.exceptions( ifstream::failbit | ifstream::badbit );
    file.ignore( MAGIC_NUMBER_CODE_SIZE );
    file.read( ( char* ) ( &blockSize ), sizeof(size_t) );
    file.close();

    return blockSize;
}

BPlusTreeFile::ControlBlock::ControlBlock( const physical::byte* block )
{
    m_magicNumber[0] = block[0];
    m_magicNumber[1] = block[1];
    m_blockSize = *( (size_t*) ( block + 2 ) );
    m_rootNodeNbr = *( ( NodeID_t* ) ( block + 2 + sizeof(size_t) ) );
}

BPlusTreeFile::ControlBlock::ControlBlock( const size_t& blockSize,
        const NodeID_t& rootNodeNbr )
{
    m_magicNumber[0] = magicNumberFirstDigit;
    m_magicNumber[1] = magicNumberSecondDigit;
    m_blockSize = blockSize;
    m_rootNodeNbr = rootNodeNbr;
}

bool BPlusTreeFile::ControlBlock::isValid() const
{
    return m_magicNumber[0] == magicNumberFirstDigit
            && m_magicNumber[1] == magicNumberSecondDigit && m_blockSize > 0;
}

size_t BPlusTreeFile::ControlBlock::getBlockSize() const
{
    return m_blockSize;
}

void BPlusTreeFile::ControlBlock::setBlockSize( const size_t& blockSize )
{
    m_blockSize = blockSize;
}

NodeID_t BPlusTreeFile::ControlBlock::getRootNodeNbr() const
{
    return m_rootNodeNbr;
}

void BPlusTreeFile::ControlBlock::setRootNodeNbr(
        const NodeID_t& rootNodeNbr )
{
    m_rootNodeNbr = rootNodeNbr;
}

void BPlusTreeFile::ControlBlock::writeInBlock(
        physical::byte* block ) const
{
    memcpy( block, this, sizeof(ControlBlock) );
}
